package com.yunxi.module.system.service.tenant.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.yunxi.framework.common.enums.CommonStatusEnum;
import com.yunxi.framework.common.exception.util.ServiceExceptionUtil;
import com.yunxi.framework.common.pojo.PageResult;
import com.yunxi.framework.common.util.collection.CollectionUtils;
import com.yunxi.framework.common.util.date.DateUtils;
import com.yunxi.framework.common.util.object.BeanUtils;
import com.yunxi.framework.tenant.config.TenantProperties;
import com.yunxi.framework.tenant.core.context.TenantContextHolder;
import com.yunxi.framework.tenant.core.util.TenantUtils;
import com.yunxi.module.system.convert.tenant.TenantConvert;
import com.yunxi.module.system.enums.constant.ErrorCodeConstants;
import com.yunxi.module.system.enums.permission.RoleCodeEnum;
import com.yunxi.module.system.enums.permission.RoleTypeEnum;
import com.yunxi.module.system.mapper.tenant.TenantMapper;
import com.yunxi.module.system.model.permission.MenuDO;
import com.yunxi.module.system.model.permission.RoleDO;
import com.yunxi.module.system.model.tenant.TenantDO;
import com.yunxi.module.system.model.tenant.TenantPackageDO;
import com.yunxi.module.system.service.permission.MenuService;
import com.yunxi.module.system.service.permission.PermissionService;
import com.yunxi.module.system.service.permission.RoleService;
import com.yunxi.module.system.service.tenant.TenantPackageService;
import com.yunxi.module.system.service.tenant.TenantService;
import com.yunxi.module.system.service.tenant.handler.TenantInfoHandler;
import com.yunxi.module.system.service.tenant.handler.TenantMenuHandler;
import com.yunxi.module.system.service.user.AdminUserService;
import com.yunxi.module.system.vo.admin.permission.role.RoleSaveReqVO;
import com.yunxi.module.system.vo.admin.tenant.tenant.TenantPageReqVO;
import com.yunxi.module.system.vo.admin.tenant.tenant.TenantSaveReqVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * <p>
 * 租户 Service 接口实现类
 * </p>
 *
 * @author lidy
 * @since 2024-05-08
 */
@Service
@Validated
@Slf4j
public class TenantServiceImpl implements TenantService {

    @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
    @Autowired(required = false) // 由于 yunxi.tenant.enable 配置项，可以关闭多租户的功能，所以这里只能不强制注入
    private TenantProperties tenantProperties;

    @Resource
    private TenantMapper tenantMapper;

    @Resource
    private TenantPackageService tenantPackageService;

    @Resource
    private PermissionService permissionService;

    @Resource
    private RoleService roleService;

    @Resource
    private MenuService menuService;

    @Resource
    private AdminUserService userService;

    /**
     * 获得名字对应的租户
     *
     * @param name 租户名
     * @return 租户
     */
    @Override
    public TenantDO getTenantByName(String name) {
        return tenantMapper.selectByName(name);
    }

    /**
     * 获得所有租户编号
     *
     * @return 租户编号集合
     */
    @Override
    public List<Long> getTenantIdList() {
        // 查询所有租户
        List<TenantDO> tenantDOS = tenantMapper.selectList();

        return CollectionUtils.convertList(tenantDOS, TenantDO::getId);
    }

    /**
     * 校验租户是否合法
     *
     * @param tenantId 租户编号
     */
    @Override
    public void validTenant(Long tenantId) {
        TenantDO tenantDO = this.getTenant(tenantId);
        // 租户不存在
        if (tenantDO == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_NOT_EXISTS);
        }
        // 租户禁用
        if (tenantDO.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_DISABLE);
        }
        // 租户过期
        if (DateUtils.isExpired(tenantDO.getExpireTime())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_EXPIRE);
        }
    }

    /**
     * 根据租户编号查询租户信息
     *
     * @param tenantId 租户编号
     * @return 租户信息
     */
    @Override
    public TenantDO getTenant(Long tenantId) {
        return tenantMapper.selectById(tenantId);
    }

    /**
     * 进行租户的信息处理逻辑
     * 其中：租户编号从 {@link com.yunxi.framework.tenant.core.context.TenantContextHolder} 上下文中获取
     *
     * @param handler 处理器
     */
    @Override
    public void handleTenantInfo(TenantInfoHandler handler) {
        // 如果禁用，则不执行逻辑
        if (isTenantDisable()) {
            return;
        }

        // 获得租户
        TenantDO tenant = getTenant(TenantContextHolder.getTenantId());

        // 执行处理器
        handler.handle(tenant);
    }

    /**
     * 租户的菜单逻辑过滤
     * 其中：租户编号从 {@link com.yunxi.framework.tenant.core.context.TenantContextHolder} 上下文中获取
     *
     * @param handler 过滤器
     */
    @Override
    public void handleTenantMenu(TenantMenuHandler handler) {
        // 如果多租户禁用，则不执行逻辑
        if (isTenantDisable()) {
            return;
        }

        // 获得租户
        TenantDO tenant = getTenant(TenantContextHolder.getTenantId());
        // 租户拥有的菜单编号
        Set<Long> menuIds;
        if (isSystemTenant(tenant)) {
            // 如果是系统租户，菜单是全量的
            menuIds = CollectionUtils.convertSet(menuService.getMenuList(), MenuDO::getId);
        } else {
            // 非系统租户，获取有权限的
            menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getMenuIds();
        }
        handler.handle(menuIds);
    }

    /**
     * 获得租户分页列表
     *
     * @param reqVO 条件
     * @return 租户分页列表
     */
    @Override
    public PageResult<TenantDO> getTenantPage(TenantPageReqVO reqVO) {
        return tenantMapper.selectPage(reqVO);
    }

    /**
     * 创建租户
     * 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
     *
     * @param createReqVO 租户信息
     * @return 租户编号
     */
    @Override
    @DSTransactional
    public Long createTenant(TenantSaveReqVO createReqVO) {
        // 校验租户名称是否重复
        validateTenantNameDuplicate(createReqVO.getName(), null);

        // 校验租户域名是否重复
        validateTenantWebsiteDuplicate(createReqVO.getWebsite(), null);

        // 校验租户套餐是否禁用
        TenantPackageDO tenantPackageDO = tenantPackageService.validateTenantPackage(createReqVO.getPackageId());

        // 创建租户
        TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class);
        tenantMapper.insert(tenant);

        // 创建租户的管理员
        TenantUtils.execute(tenant.getId(), () -> {
            // 创建角色
            Long roleId = createRole(tenantPackageDO);

            // 创建用户，并分配角色
            Long userId = createUser(roleId, createReqVO);

            // 修改租户的管理员
            tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
        });

        return tenant.getId();
    }

    /**
     * 更新租户信息
     * 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
     *
     * @param updateReqVO 租户信息
     */
    @Override
    @DSTransactional
    public void updateTenant(TenantSaveReqVO updateReqVO) {
        // 校验租户是否可更新
        TenantDO tenantDO = validateUpdateTenant(updateReqVO.getId());

        // 校验租户名称是否重复
        validateTenantNameDuplicate(updateReqVO.getName(), updateReqVO.getId());

        // 校验租户域名是否重复
        validateTenantWebsiteDuplicate(updateReqVO.getWebsite(), updateReqVO.getId());

        // 校验租户套餐是否禁用
        TenantPackageDO tenantPackageDO = tenantPackageService.validateTenantPackage(updateReqVO.getPackageId());

        // 更新租户
        TenantDO updateObj = BeanUtils.toBean(updateReqVO, TenantDO.class);
        tenantMapper.updateById(updateObj);

        // 如果租户套餐发生改变,则删除角色权限
        if (ObjectUtil.notEqual(tenantDO.getPackageId(), updateReqVO.getPackageId())) {
            updateTenantRoleMenu(tenantDO.getId(), tenantPackageDO.getMenuIds());
        }
    }

    /**
     * 更新租户角色菜单权限
     *
     * @param tenantId 租户编号
     * @param menuIds  菜单编号集合
     */
    @Override
    @DSTransactional
    public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) {
        TenantUtils.execute(tenantId, () -> {
            // 获得所有角色
            List<RoleDO> roleList = roleService.getRoleList();
            // 兜底校验
            roleList.forEach(role -> {
                // 如果是租户管理员,重新分配其权限为租户套餐的权限
                if (Objects.equals(role.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) {
                    permissionService.assignRoleMenu(role.getId(), menuIds);
                    log.info("[updateTenantRoleMenu][租户管理员({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), menuIds);
                    return;
                }
                // 如果是其他角色,则去掉超过套餐的权限
                Set<Long> roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId());
                roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
                permissionService.assignRoleMenu(role.getId(), roleMenuIds);
                log.info("[updateTenantRoleMenu][租户管理员({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds);
            });
        });
    }

    /**
     * 删除租户
     *
     * @param tenantId 租户编号
     */
    @Override
    public void deleteTenant(Long tenantId) {
        // 校验
        validateUpdateTenant(tenantId);
        // 删除
        tenantMapper.deleteById(tenantId);
    }

    /**
     * 根据租户域名,获得租户信息
     *
     * @param website 租户域名
     * @return 租户信息
     */
    @Override
    public TenantDO getTenantByWebsite(String website) {
        return tenantMapper.selectByWebsite(website);
    }


    /**
     * 获得使用指定套餐的租户数组
     *
     * @param packageId 租户套餐编号
     * @return 租户数组
     */
    @Override
    public List<TenantDO> getTenantListByPackageId(Long packageId) {
        return tenantMapper.selectListByPackageId(packageId);
    }

    /**
     * 根据租户套餐编号查询租户数量
     *
     * @param packageId 租户套餐编号
     * @return 租户数量
     */
    @Override
    public Long getTenantCountByPackageId(Long packageId) {
        return tenantMapper.selectCountByPackageId(packageId);
    }

    /**
     * 校验租户是否存在
     *
     * @param tenantId 租户编号
     * @return 租户信息
     */
    private TenantDO validateUpdateTenant(Long tenantId) {
        // 查询
        TenantDO tenantDO = tenantMapper.selectById(tenantId);
        if (tenantDO == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_NOT_EXISTS);
        }
        // 内置租户,不允许删除
        if (isSystemTenant(tenantDO)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_CAN_NOT_UPDATE_SYSTEM);
        }
        return tenantDO;
    }

    /**
     * 创建用户,并分配权限
     *
     * @param roleId      角色编号
     * @param createReqVO 用户信息
     * @return 用户编号
     */
    private Long createUser(Long roleId, TenantSaveReqVO createReqVO) {
        // 创建用户
        Long userId = userService.createUser(TenantConvert.INSTANCE.convert02(createReqVO));

        // 分配角色
        permissionService.assignUserRole(userId, Collections.singleton(roleId));

        return userId;
    }

    /**
     * 创建角色
     *
     * @param tenantPackageDO 租户套餐编号
     * @return 角色编号
     */
    private Long createRole(TenantPackageDO tenantPackageDO) {
        RoleSaveReqVO reqVO = new RoleSaveReqVO();
        reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName());
        reqVO.setCode(RoleCodeEnum.TENANT_ADMIN.getCode());
        reqVO.setSort(0);
        reqVO.setRemark("系统自动生成");

        Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());

        // 分配权限
        permissionService.assignRoleMenu(roleId, tenantPackageDO.getMenuIds());

        return roleId;
    }

    /**
     * 校验租户域名是否重复
     *
     * @param website  租户域名
     * @param tenantId 租户编号
     */
    private void validateTenantWebsiteDuplicate(String website, Long tenantId) {
        if (StrUtil.isEmpty(website)) {
            return;
        }
        // 根据租户域名查询
        TenantDO tenantDO = tenantMapper.selectByWebsite(website);
        if (tenantDO == null) {
            return;
        }
        // 如果 tenantId 为空，说明不用比较是否为相同 ID 的租户
        if (tenantId == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_WEBSITE_DUPLICATE, website);
        }
        if (!tenantDO.getId().equals(tenantId)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_WEBSITE_DUPLICATE, website);
        }
    }

    /**
     * 校验租户名称是否重复
     *
     * @param name     租户名称
     * @param tenantId 租户编号
     */
    private void validateTenantNameDuplicate(String name, Long tenantId) {
        // 根据租户名查询
        TenantDO tenantDO = tenantMapper.selectByName(name);
        if (tenantDO == null) {
            return;
        }
        // 如果 tenantId 为空，说明不用比较是否为相同 ID 的租户
        if (tenantId == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_NAME_DUPLICATE, name);
        }
        if (!tenantId.equals(tenantDO.getId())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENANT_NAME_DUPLICATE, name);
        }
    }

    /**
     * 判断租户是否是系统租户
     *
     * @param tenant 租户信息
     * @return 判断结果
     */
    private boolean isSystemTenant(TenantDO tenant) {
        return Objects.equals(tenant.getPackageId(), TenantDO.PACKAGE_ID_SYSTEM);
    }

    /**
     * 校验租户是否禁用
     *
     * @return 校验结果
     */
    private boolean isTenantDisable() {
        return tenantProperties == null || Boolean.FALSE.equals(tenantProperties.getEnable());
    }
}
